﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Threading;
using ThreadState = System.Threading.ThreadState;

namespace HotShot.Service.BusinessLogic
{
    /// <summary>
    /// Debug info event args inherited from EventArgs.
    /// </summary>
    public class DebugEventArgs : EventArgs
    {
        public DebugEventArgs(string message)
        {
            Message = message;
        }

        public string Message { get; set; }
    }

    /// -----------------------------------------------------------------------
    /// <summary>
    /// Periódikusan végrehajtandó háttérfeldolgozást végző osztályok absztrakt őse.
    /// </summary>
    /// <remarks>
    /// A leszármazottnak meg kell valósítania a Work() metódust.
    /// Ezen kívül egyedi inicializáli lépéseket az Initialize() override-olásával
    /// végezhet, de meg kell hívnia az ős Initialize metódusát is.
    /// </remarks>
    /// -----------------------------------------------------------------------
    public abstract class WorkerBase
    {
        protected abstract Guid ProcessorId { get; }

        /// <summary>
        /// A programszál, amelyen a folyamat végrehajtódik
        /// </summary>
        private Thread _workerThread;

        /// <summary>
        /// A folyamat megszakítását jelző WaitHandle
        /// </summary>
        protected ManualResetEvent StopSignal = new ManualResetEvent(false);

        /// <summary>
        /// A feldolgozási időköz másodpercben.
        /// </summary>
        protected int PeriodicityMilliSec;

        /// <summary>
        /// A feladatot végrehajtó gép neve.
        /// </summary>
        protected string ExecutorMachineName { get; private set; }

        /// <summary>
        /// A feladatvégrehajtó neve.
        /// </summary>
        protected string ProcessorName { get; private set; }

        /// <summary>
        /// Akkor következik be, ha az aszinkron végrehajtás befejeződik, 
        /// megszakítják vagy kivétel keletkezik.
        /// </summary>
        public event EventHandler<AsyncCompletedEventArgs> ExecuteCompleted;

        /// <summary>
        /// Akkor következik be, ha a végrehajtásról üzenet
        /// </summary>
        public event EventHandler<DebugEventArgs> Debug;

        public bool Disabled { get; set; }

        public virtual bool ShouldRunEarly { get {
            return false;
        } }

        /// ------------------------------------------------
        /// <summary>
        /// Inicializálja a folyamatot.
        /// </summary>
        /// <param name="executorMachineName">Futtató gép neve.</param>
        /// <param name="processorName">A feldolgozó neve.</param>
        /// <param name="periodicitySec"></param>
        /// ------------------------------------------------
        public virtual void Initialize(string executorMachineName, string processorName, int periodicityMilliSec)
        {
            Trace.TraceInformation("Initialize processor: {0} (machinename: {1}, polling time: {2} ms)", processorName, executorMachineName, periodicityMilliSec);
            ExecutorMachineName = executorMachineName;
            ProcessorName = processorName;
            PeriodicityMilliSec = periodicityMilliSec;
        }

        /// ------------------------------------------------
        /// <summary>
        /// Végrehajtja a feldogozó egységnyi munkáját.
        /// </summary>
        /// <remarks>
        /// A Work() függvényt fogja futtatni az ütemező, beállítható módon több szálon. A függvényt tehát
        /// szálbiztosra kell megírni. Azaz:
        ///  - Lehetőleg ne használjunk külső változókat (se osztályváltozókat, se property-ket, se statikus osztályok változóit). 
        ///  - Ha mégis kell, akkor lehetőség szerint csak olvassuk a tartalmát. 
        ///  - Ha írni is kell, akkor minden hozzáférést lock() blokkal kell megvédeni (az olvasást is). Sose a kiolvasandó property-t lockoljuk, 
        ///    hanem hozzunk létre mellé egy <code>public static readonly object _{propertyneve}Lock = new object();</code> változót, és azt.
        ///  - Külső függvények hívásakor győződjünk meg róla, hogy szálbiztos műveleteket használunk (a dokumentációból kiderül, vagy a fentieket 
        ///    kell ellenőrizni rájuk is). Ha nem szálbiztosak, azokat is lockolni kell (itt viszont lehet az őket tartalmazó objektumpéldányt lockolni)
        ///  - Ne indítsunk szálat a Work() függvényből. Azaz kerüljük az aszinkron műveleteket, backgroundworker, threadpool és a thread használatát.
        ///  - Ne várakozzunk a Work() függvényben sokáig. Ha mégis várakozni akarunk, <code>Thread.Sleep({idő})</code> helyett használjuk a 
        ///    <code>if(!_stopSignal.WaitOne({idő})) return; // abortáltak minket, kilépés</code> szerkezetet.
        ///    
        /// A függvény mindig egy darab műveletet végezzen, a ciklusok szervezését vagy többszálúsítást az ütemezővel kell megoldani.
        /// </remarks>
        /// ------------------------------------------------
        protected abstract bool Work();

        /// ------------------------------------------------
        /// <summary>
        /// Aszinkron feladat indítása.
        /// </summary>
        /// ------------------------------------------------
        public void ExecuteAsync()
        {
            StopSignal.Reset();

            _workerThread = new Thread(i =>
                                           {
                                               var o = i as WorkerBase;
                                               Trace.CorrelationManager.ActivityId = o == null ? ProcessorId : o.ProcessorId;

                                               Trace.Write("Processor started: " + o.ProcessorName);

                                               while (true)
                                               {
                                                   Trace.CorrelationManager.ActivityId = o == null ? ProcessorId : o.ProcessorId;
                                                   
                                                   if(o == null)
                                                   {
                                                       Trace.TraceError("Processor thread received null (class: {0}", GetType());
                                                       break;
                                                   }

                                                   bool shouldWait = true;

                                                   try
                                                   {
                                                       
                                                       if (!ShouldRunEarly && StopSignal.WaitOne(shouldWait ? o.PeriodicityMilliSec : 0))
                                                       {
                                                           break;
                                                       }

                                                       //Trace.TraceInformation("Processor work tick: {0}", o.ProcessorName);
                                                       if (!Disabled && ShouldWork())
                                                       {
                                                           //Trace.TraceInformation("Processor work: {0}", o.ProcessorName);
                                                           shouldWait = !Work();
                                                           OnAfterWork();
                                                       }

                                                       if (ShouldRunEarly && StopSignal.WaitOne(shouldWait ? o.PeriodicityMilliSec : 0))
                                                       {
                                                           break;
                                                       }                    
                                                   }
                                                   catch (Exception e)
                                                   {
                                                       Trace.TraceError(e.Message);
                                                       shouldWait = true;
                                                   }
                                               }

                                               // ide csak a feladat végrehajtásának megszakítása esetén jut a vezérlés
                                               OnCompleted(new AsyncCompletedEventArgs(null, true, null));

                                               Trace.Write("Processor stopped: " + o.ProcessorName);
                                           });
            _workerThread.Name = ProcessorName;
            _workerThread.Start(this);
        }

        /// ------------------------------------------------
        /// <summary>
        /// Aszinkron feladat megszakítása.
        /// </summary>
        /// ------------------------------------------------
        public void ExecuteAsyncCancel()
        {
            StopSignal.Set();

            if ((_workerThread.ThreadState & ThreadState.Unstarted) != ThreadState.Unstarted)
            {
                _workerThread.Join();
            }
            else
            {
                OnCompleted(new AsyncCompletedEventArgs(null, true, null));
            }
        }

        /// ------------------------------------------------
        /// <summary>
        /// Szinkron feladat indítása.
        /// </summary>
        /// ------------------------------------------------
        public void ExecuteOnce()
        {
            _workerThread = new Thread(ExecuteWorkerOnce);
            _workerThread.Start();
        }

        /// ------------------------------------------------
        /// <summary>
        /// Elsüti az ExecuteCompleted eseményt.
        /// </summary>
        /// <param name="e"></param>
        /// ------------------------------------------------
        protected void OnCompleted(AsyncCompletedEventArgs e)
        {
            if (ExecuteCompleted != null)
            {
                ExecuteCompleted(this, e);
            }
        }

        protected virtual void OnAfterWork()
        {
        }

        /// ------------------------------------------------
        /// <summary>
        /// Elsüti a Debug eseményt.
        /// </summary>
        /// <param name="e"></param>
        /// ------------------------------------------------
        protected void OnDebug(DebugEventArgs e)
        {
            if (Debug != null)
            {
                Debug(this, e);
            }
        }

        #region Private methods

        /// -------------------------------------------------------------------
        /// <summary>
        /// Végtelen ciklusban hívja a Work() metódust.
        /// Ez a metódus a _workerThread-ben tárolt programszálon fut.
        /// A ciklust az ExecuteAsyncCancel() metódus tudja megtörni
        /// a _waitHandle bebillentésével.
        /// </summary>
        /// -------------------------------------------------------------------
        protected virtual void ExecuteWorker()
        {
            while (true)
            {
                try
                {
                    if (!ShouldRunEarly && StopSignal.WaitOne(PeriodicityMilliSec))
                    {
                        break;
                    }

                    if (ShouldWork())
                    {
                        Work();
                    }

                    if (ShouldRunEarly && StopSignal.WaitOne(PeriodicityMilliSec * 1000))
                    {
                        break;
                    }                    
                }
                catch (Exception e)
                {
                    Trace.TraceWarning(e.Message);
                }
            }

            // ide csak a feladat végrehajtásának megszakítása esetén jut a vezérlés
            OnCompleted(new AsyncCompletedEventArgs(null, true, null));
        }

        /// -------------------------------------------------------------------
        /// <summary>
        /// Egyszer hívja a Work() metódust.
        /// Ez a metódus a _workerThread-ben tárolt programszálon fut.
        /// </summary>
        /// -------------------------------------------------------------------
        private void ExecuteWorkerOnce()
        {
            try
            {
                Work();
                OnCompleted(new AsyncCompletedEventArgs(null, false, null));
            }
            catch (Exception e)
            {
                OnCompleted(new AsyncCompletedEventArgs(e, false, null));
            }
        }

        // --------------------------------------------------------------------
        /// <summary>
        /// Ellenőrzi, hogy a Work() műveletnek futnia kell-e
        /// </summary>
        /// <remarks>
        /// Normális esetben minden polling ciklusban futtatnunk kell, a 
        /// műveletet olyankor kell felülírni, ahol valami extra feltétel
        /// teljesüléséhez akarjuk kötni a futást (pl. adott időponthoz)
        /// </remarks>
        // --------------------------------------------------------------------
        protected virtual bool ShouldWork()
        {
            return true;
        }

        #endregion
    }
}
